﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using Quail;
using QuailDashboard.View.Converters;
using QuailDashboard.View.Data;
using QuailDashboard.View.Helpers;
using QuailDashboard.View.Models;
using QuailDashboard.View.QuailServiceReference;

namespace QuailDashboard.View.Controls
{
    /// <summary>
    /// The test run log control
    /// </summary>
    public partial class TestRunLogsControl : IDisposable
    {
        private static readonly List<TestRunLogsControl> ActiveControls = new List<TestRunLogsControl>();
        public static readonly DependencyProperty TestRunIdProperty = DependencyProperty.Register(
                    "TestRunId", 
                    typeof(int),
                    typeof(TestRunLogsControl),
                    new PropertyMetadata(TestRunIdPropertyChanged));

        public static readonly DependencyProperty RunStatusResultProperty = DependencyProperty.Register(
                    "RunStatusResult",
                    typeof(RunStatusResultWrapper),
                    typeof(TestRunLogsControl),
                    new PropertyMetadata(RunStatusResultPropertyChanged));

        public static readonly DependencyProperty ShowPopoutProperty = DependencyProperty.Register(
                    "ShowPopout",
                    typeof(bool),
                    typeof(TestRunLogsControl),
                    new PropertyMetadata(true, ShowPopoutPropertyChanged));

        private readonly TestResultsPageState _pageState = new TestResultsPageState();
        private readonly QuailDataServiceClient _service;
        private bool _infixcontrolsizes, _selectfirstresult;
        private IList<SearchTestRunTaskResult> _listSearchResults;

        /// <summary>
        /// Initializes a new instance of the <see cref="TestRunLogsControl"/> class.
        /// </summary>
        public TestRunLogsControl()
        {
            InitializeComponent();

            TreeViewTestResults.FindTreeViewItemComplete += OnFindTreeViewItemComplete;
            _service = QuailDataServiceFactory.CreateClient();
            _service.GetTestRunCompleted += Webservice_GetTestRunCompleted;
            _service.GetTestRunTaskCompleted += Webservice_GetTestRunTaskCompleted;
            _service.GetAllTestRunTaskLogEntryCompleted += Webservice_GetAllTestRunTaskLogEntryCompleted;
            _service.SearchForTestRunTaskCompleted += OnSearchForTestRunTaskCompleted;
            _pageState.PropertyChanged += OnPageStatePropertyChanged;
            _pageState.FilterChanged += OnLogEntriesFilterChanged;

            lock (ActiveControls)
                ActiveControls.Add(this);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            lock (ActiveControls)
                ActiveControls.Remove(this);
        }

        public static bool IsSelectedTestRun(int testrunid)
        {
            lock (ActiveControls)
                return ActiveControls.Any(hps => hps.TestRunId == testrunid);
        }

        /// <summary>
        /// Gets or sets a value indicating whether to show the popout button
        /// </summary>
        public bool ShowPopout
        {
            get
            {
                return (bool)GetValue(ShowPopoutProperty);
            }

            set
            {
                SetValue(ShowPopoutProperty, value);
            }
        }

        public RunStatusResultWrapper RunStatusResult
        {
            get
            {
                return (RunStatusResultWrapper)GetValue(RunStatusResultProperty);
            }

            set
            {
                SetValue(RunStatusResultProperty, value);
            }
        }

        private static ICollection<LogStatus> DefaultStatusList
        {
            get
            {
                return new List<LogStatus>
                           {
                               LogStatus.Fail,
                               LogStatus.Warning,
                               LogStatus.Abandoned,
                               LogStatus.FailYsod,
                               LogStatus.FailQuail,
                               LogStatus.FailSync,
                               LogStatus.FailThirdParty,
                               LogStatus.FailTimeout
                           };
            }
        }

        private void OnRunStatusResultChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "TestRunId")
                RefreshData();
        }

        /// <summary>
        /// Gets or sets the Test Run Id to display
        /// </summary>
        public int TestRunId
        {
            get
            {
                var rsr = RunStatusResult;

                if (rsr != null)
                    return rsr.TestRunId;

                return (int)GetValue(TestRunIdProperty);
            }
            
            set
            {
                SetValue(TestRunIdProperty, value);
            }
        }

        private TestRunTaskData SelectedTestRunTaskData
        {
            get
            {
                return TreeViewTestResults.SelectedItem as TestRunTaskData;
            }
        }

        private IList<TestRunTaskData> TestRunTaskDataRoot
        {
            get
            {
                return TreeViewTestResults.ItemsSource as IList<TestRunTaskData>;
            }
        }

        public void RefreshData()
        {
            var testrunid = TestRunId;
            _service.GetTestRunAsync(testrunid);
            _service.GetTestRunTaskAsync(testrunid, 0);

            LoadAllLogs();
        }

        private static void RunStatusResultPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
        {
            // reset the propertychanged event, then refresh the data
            var me = (TestRunLogsControl) sender;
            var oldrsr = (RunStatusResultWrapper)args.OldValue;
            var newrsr = (RunStatusResultWrapper)args.NewValue;

            if (oldrsr != null)
                oldrsr.PropertyChanged -= me.OnRunStatusResultChanged;

            if (newrsr != null)
                newrsr.PropertyChanged += me.OnRunStatusResultChanged;

            me.RefreshData();
        }

        
        private static void TestRunIdPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
        {
            // start up getting the test run data
            var me = (TestRunLogsControl) sender;
            me.RefreshData();
        }

        private static void ShowPopoutPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
        {
            var me = (TestRunLogsControl)sender;

            me._pageState.ShowPopout = me.ShowPopout;
        }

        private void OnLayoutRootLoaded(object sender, RoutedEventArgs e)
        {
            LayoutRoot.DataContext = _pageState;
            FixControlSizes();
            AllLogEntries.OnSyncToTestRunTaskId += OnSyncTestRunTaskId;
        }

        private void OnSyncTestRunTaskId(long testruntaskid)
        {
            DoFind(testruntaskid);
        }

        private void Webservice_GetTestRunCompleted(object sender, GetTestRunCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                App.CurrentApp.ShowError("Error getting the TestRun", e.Error);
                return;
            }

            var result = e.Result;
            _pageState.TestRunStatusId = result.RunStatusId;
            var converter = new Converters.DurationConverter();

            _pageState.TestRunDetails = result.TestName + " " + result.RunStartDate + " (" + converter.Convert(result.Duration, typeof(DateTime), null, CultureInfo.CurrentCulture) + ")";
        }

        private void Webservice_GetTestRunTaskCompleted(object sender, GetTestRunTaskCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                App.CurrentApp.ShowError("Error getting the TestRunTask", e.Error);
                return;
            }


            var rsr = RunStatusResult == null || RunStatusResult.Duration.HasValue ? null : RunStatusResult;

            var results = TestRunTaskData.MakeList(rsr, null, e.Result);
            foreach (var task in results)
                task.Populate(false);

            _selectfirstresult = true;
            TreeViewTestResults.ItemsSource = results;
        }

        private void Webservice_GetAllTestRunTaskLogEntryCompleted(object sender, GetAllTestRunTaskLogEntryCompletedEventArgs e)
        {
            _pageState.DecProcessing();
            if (e.Error != null) 
                App.CurrentApp.ShowError("Error getting all TestRunTaskLogEntries", e.Error);
            else if (_pageState.ShowAllLogs)
                AllLogEntries.ItemsSource = FilteredLogEntries(e.Result);
            else
                AllLogEntries.ItemsSource = null;
        }

        private void TreeViewTestResults_LayoutUpdated(object sender, EventArgs e)
        {
            if (_selectfirstresult)
            {
                foreach (IHierarchalData item in TreeViewTestResults.Items)
                {
                    TreeViewTestResults.FindTreeViewItem(item);
                    _selectfirstresult = false;
                }
            }
        }

        private void TreeViewTestResults_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            var data = TreeViewTestResults.SelectedItem as TestRunTaskData;

            TaskDetailGrid.DataContext = data;

            if (data == null)
            {
                GridLogEntries.ItemsSource = null;
                GridAttributes.ItemsSource = null;
            }
            else
            {
                GridLogEntries.ItemsSource = data.LogEntries;
                GridAttributes.ItemsSource = data.Attributes;
            }

            _pageState.ShowLogEntriesPagingControls = true;  // GridLogEntries.ShowPagingControls;
        }

        private void OnLayoutRootSizedChanged(object sender, SizeChangedEventArgs e)
        {
            FixControlSizes();
        }

        private void OnGridTestResults_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            FixControlSizes();
        }

        /// <summary>
        /// Attempts to resize the controls to keep them in the browser frame.  where the heck is the "Anchor" property?!!?
        /// </summary>
        private void FixControlSizes()
        {
            if (_infixcontrolsizes)
                return;

            _infixcontrolsizes = true;

            var gt = RightGrid.TransformToVisual(ContentScrollViewer);
            var offset = gt.Transform(new Point(0, 0));

            // var h = Application.Current.Host.Content.ActualHeight - offset.Y - 10;
            // var w = Application.Current.Host.Content.ActualWidth - offset.X - 10;
            var h = ContentScrollViewer.ViewportHeight - offset.Y;
            var w = ContentScrollViewer.ViewportWidth - offset.X;
            RightGrid.Height = h;
            RightGrid.MaxHeight = h;
            RightGrid.MaxWidth = w;
            RightGrid.Width = w;

            SectionAllLogEntries.Height = h;
            SectionAllLogEntries.MaxHeight = h;

            gt = TreeViewTestResults.TransformToVisual(ContentScrollViewer);
            offset = gt.Transform(new Point(0, 0));
            TreeViewTestResults.Height = ContentScrollViewer.ViewportHeight - offset.Y;

            _infixcontrolsizes = false;
        }

        private void SearchBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
                DoSearchBoxSearch();
        }

        private void SearchBox_Click(object sender, RoutedEventArgs e)
        {
            DoSearchBoxSearch();
        }

        private void DoSearchBoxSearch()
        {
            // Search for whatever the user typed in
            DoFind(SearchBox.Text, null, true);
        }

        private void OnSearchForTestRunTaskCompleted(object sender, SearchForTestRunTaskCompletedEventArgs e)
        {
            _listSearchResults = e.Result;

            var found = false;

            if (_listSearchResults.Count > 0)
            {
                long testruntaskid = _listSearchResults[0].TestRunTaskId.Value;

                _listSearchResults.RemoveAt(0);

                foreach (var data in TestRunTaskDataRoot)
                {
                    if (data.TestRunTaskId == testruntaskid)
                    {
                        found = true;
                        TreeViewTestResults.FindTreeViewItem(data, _listSearchResults.Count == 0);
                        break;
                    }
                }
            }

            if (!found)
            {
                _listSearchResults = null;
                _pageState.IsSearching = false;
            }
        }

        private void OnFindTreeViewItemComplete(object sender, VirtualizingTreeView.VirtualizingTreeViewItem tvitem,
            IHierarchalData rawdata)
        {
            if (_listSearchResults != null)
            {
                if (tvitem == null)
                {
                    // search failed
                    _listSearchResults = null;
                    MessageBox.Show("Unable to find all the tasks for the search result (y)!");
                }
                else if (CheckSearchResults((TestRunTaskData)rawdata))
                    return;
            }

            _pageState.IsSearching = false;
        }

        private bool CheckSearchResults(TestRunTaskData data)
        {
            if (_listSearchResults.Count == 0)
            {
                // search is complete
                _listSearchResults = null;
            }
            else
            {
                // search is still on-going
                if (!data.IsPopulated)
                {
                    data.PopulateComplete += DataPopulationComplete;
                    return true;
                }

                var testruntaskid = _listSearchResults[0].TestRunTaskId.Value;
                _listSearchResults.RemoveAt(0);

                foreach (var childdata in data.ChildTasks)
                {
                    if (childdata.TestRunTaskId == testruntaskid)
                    {
                        TreeViewTestResults.FindTreeViewItem(childdata, _listSearchResults.Count == 0);
                        return true;
                    }
                }

                _listSearchResults = null;
                MessageBox.Show("Unable to find all the tasks for the search result (x)!");
            }

            return false;
        }

        private void DataPopulationComplete(object sender, EventArgs e)
        {
            var data = (TestRunTaskData)sender;
            data.PopulateComplete -= DataPopulationComplete;

            if (!CheckSearchResults(data) && _pageState.IsSearching)
                _pageState.IsSearching = false;
        }

        private void DoFind(string text, ICollection<LogStatus> statuses, bool ascending)
        {
            // Search for whatever the user typed in
            if (_pageState.IsSearching)
                return;

            var data = SelectedTestRunTaskData;
            string statuslist = null;
            if (statuses != null && statuses.Count > 0)
            { 
                
                foreach (var status in statuses)
                {
                    if (statuslist == null)
                        statuslist = ((int)status).ToString();
                    else
                        statuslist += "," + ((int)status);
                }
            }

            _service.SearchForTestRunTaskAsync(
                TestRunId,
                data == null ? (long?)null : data.TestRunTaskId,
                ascending,
                text,
                statuslist);
            _pageState.IsSearching = true;
        }

        private void DoFind(long testruntaskid)
        {
            // Search for whatever the user typed in
            if (_pageState.IsSearching)
                return;

            _service.SearchForTestRunTaskAsync(
                TestRunId,
                testruntaskid,
                true,
                null,
                null);
            _pageState.IsSearching = true;
        }

        private void ErrorPrior_Click(object sender, RoutedEventArgs e)
        {
            DoFind(null, DefaultStatusList, false);
        }

        private void ErrorNext_Click(object sender, RoutedEventArgs e)
        {
            DoFind(null, DefaultStatusList, true);
        }

        private void Popout_Click(object sender, RoutedEventArgs e)
        {
            var currentUrl = HtmlPage.Document.DocumentUri;

            HtmlPage.Window.Navigate(new Uri(currentUrl.AbsolutePath + string.Format("#/Detail/{0}", TestRunId), UriKind.RelativeOrAbsolute), "_blank");
        }

        private void OnPageStatePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "ShowAllLogs")
            {
                ColumnLogs.Width = new GridLength(_pageState.ShowAllLogs ? 400 : 0);
                ColumnLogSplitter.Width = new GridLength(_pageState.ShowAllLogs ? 5 : 0);

                LoadAllLogs();
            }
        }

        private void LoadAllLogs()
        {
            if (_pageState.ShowAllLogs)
            {
                _pageState.IncProcessing("Loading Logs...");
                _service.GetAllTestRunTaskLogEntryAsync(TestRunId);
            }
            else
                AllLogEntries.ItemsSource = null;
        }

        private void ShowLogs_Click(object sender, RoutedEventArgs e)
        {
            _pageState.ShowAllLogs = !_pageState.ShowAllLogs;
        }

        private void OnLogEntriesFilterChanged(object sender, EventArgs e)
        {
            AllLogEntries.ItemsSource = FilteredLogEntries(AllLogEntries.ItemsSource);
        }

        private ObservableCollection<TestRunTaskLogEntry> FilteredLogEntries(object o)
        {
            var items = o as ObservableCollection<TestRunTaskLogEntry>;

            return items;
        }

        private void FilterLogEntries_Click(object sender, RoutedEventArgs e)
        {
            var b = sender as ToggleButton;

            if (b != null && b.Tag is string)
            {
                switch ((string)b.Tag)
                {
                    case "YSOD":
                        _pageState.FilterFailedYSOD = !_pageState.FilterFailedYSOD;
                        break;
                    case "Fail":
                        _pageState.FilterFailed = !_pageState.FilterFailed;
                        break;
                    case "Pass":
                        _pageState.FilterPassed = !_pageState.FilterPassed;
                        break;
                    case "Warning":
                        _pageState.FilterWarning = !_pageState.FilterWarning;
                        break;
                    case "Pending":
                        _pageState.FilterPending = !_pageState.FilterPending;
                        break;
                    case "AsyncPending":
                        _pageState.FilterAsyncPending = !_pageState.FilterAsyncPending;
                        break;
                }
            }
        }

        /// <summary>
        /// State class for the TestResults page
        /// </summary>
        public class TestResultsPageState : IsProcessingStateModel
        {
            private bool _issearching, _showpopout = true, _showAllLogs, _showLogEntriesPagingControls, _showAllLogEntriesPagingControls;
            private string _testrundetails;
            private int _testrunstatusid;
            private readonly FilterModel _filterModel = new FilterModel();

            /// <summary>
            /// Initializes a new instance of the <see cref="TestResultsPageState"/> class.
            /// </summary>
            public TestResultsPageState()
            {
                _filterModel.PropertyChanged += OnFilterModelPropertyChanged;
                _filterModel.FilterChanged += OnFilterModelChanged;
            }

            /// <summary>
            /// Fired when a filter-related item changes
            /// </summary>
            public event EventHandler FilterChanged;


            /// <summary>
            /// Gets or sets the test run details for display
            /// </summary>
            public string TestRunDetails
            {
                get
                {
                    return _testrundetails;
                }

                set
                {
                    _testrundetails = value;
                    NotifyPropertyChanged("TestRunDetails");
                }
            }

            /// <summary>
            /// Gets or sets the TestRunStatusId
            /// </summary>
            public int TestRunStatusId
            {
                get
                {
                    return _testrunstatusid;
                }

                set
                {
                    _testrunstatusid = value;
                    NotifyPropertyChanged("TestRunStatusId");
                }
            }

            /// <summary>
            /// Gets or sets a value indicating whether a search is currently active
            /// </summary>
            public bool IsSearching
            {
                get
                {
                    return _issearching;
                }

                set
                {
                    if (_issearching != value)
                    {
                        _issearching = value;
                        NotifyPropertyChanged("IsSearching");
                    }
                }
            }

            /// <summary>
            /// Gets or sets a value indicating whether to show the popout button
            /// </summary>
            public bool ShowPopout
            {
                get
                {
                    return _showpopout;
                }

                set
                {
                    if (_showpopout != value)
                    {
                        _showpopout = value;
                        NotifyPropertyChanged("ShowPopout");
                    }
                }
            }

            /// <summary>
            /// Gets or sets a value indicating whether to show "log entries" paging controls.
            /// </summary>
            public bool ShowLogEntriesPagingControls
            {
                get
                {
                    return _showLogEntriesPagingControls;
                }

                set
                {
                    if (value != _showLogEntriesPagingControls)
                    {
                        _showLogEntriesPagingControls = value;
                        NotifyPropertyChanged("ShowLogEntriesPagingControls");
                       
                    }
                }
            }

            /// <summary>
            /// Gets or sets a value indicating whether show "all log entries" paging controls.
            /// </summary>
            public bool ShowAllLogEntriesPagingControls
            {
                get
                {
                    return _showAllLogEntriesPagingControls;
                }

                set
                {
                    if (value != _showAllLogEntriesPagingControls)
                    {
                        _showAllLogEntriesPagingControls = value;
                        NotifyPropertyChanged("ShowAllLogEntriesPagingControls");

                    }
                }
            }

            /// <summary>
            /// Gets or sets a value indicating whether to show all logs.
            /// </summary>
            public bool ShowAllLogs
            {
                get
                {
                    return _showAllLogs;
                }

                set
                {
                    if (_showAllLogs != value)
                    {
                        _showAllLogs = value;
                        NotifyPropertyChanged("ShowAllLogs");
                    }
                }
            }

            /// <summary>
            /// Gets or sets a value indicating whether to filter YSOD records
            /// </summary>
            public bool FilterFailedYSOD
            {
                get
                {
                    return _filterModel.FilterFailedYSOD;
                }

                set
                {
                    _filterModel.FilterFailedYSOD = value;
                }
            }

            /// <summary>
            /// Gets or sets a value indicating whether to filter failed records
            /// </summary>
            public bool FilterFailed
            {
                get
                {
                    return _filterModel.FilterFailed;
                }

                set
                {
                    _filterModel.FilterFailed = value;
                }
            }

            /// <summary>
            /// Gets or sets a value indicating whether to filter passed records
            /// </summary>
            public bool FilterPassed
            {
                get
                {
                    return _filterModel.FilterPassed;
                }

                set
                {
                    _filterModel.FilterPassed = value;
                }
            }

            /// <summary>
            /// Gets or sets a value indicating whether to filter warning records
            /// </summary>
            public bool FilterWarning
            {
                get
                {
                    return _filterModel.FilterWarning;
                }

                set
                {
                    _filterModel.FilterWarning = value;
                }
            }

            /// <summary>
            /// Gets or sets a value indicating whether to filter pending records
            /// </summary>
            public bool FilterPending
            {
                get
                {
                    return _filterModel.FilterPending;
                }

                set
                {
                    _filterModel.FilterPending = value;
                }
            }

            public Boolean FilterAsyncPending
            {
                get { return _filterModel.FilterAsyncPending; }
                set { _filterModel.FilterAsyncPending = value; }
            }

            /// <summary>
            /// Gets or sets the filter string
            /// </summary>
            public string FilterString
            {
                get
                {
                    return _filterModel.FilterString;
                }

                set
                {
                    _filterModel.FilterString = value;
                }
            }

            private void OnFilterModelPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                NotifyPropertyChanged(e.PropertyName);
            }

            private void OnFilterModelChanged(object sender, EventArgs e)
            {
                if (FilterChanged != null)
                    FilterChanged(this, EventArgs.Empty);
            }
        }

        private void LogsHome_Click(object sender, RoutedEventArgs e)
        {
            GridLogEntries.PageNumber = 1;
        }

        private void LogsPrior_Click(object sender, RoutedEventArgs e)
        {
            GridLogEntries.PageNumber = GridLogEntries.PageNumber - 1;
        }

        private void LogsNext_Click(object sender, RoutedEventArgs e)
        {
            GridLogEntries.PageNumber = GridLogEntries.PageNumber + 1;
        }

        private void LogsEnd_Click(object sender, RoutedEventArgs e)
        {
            GridLogEntries.PageNumber = GridLogEntries.PageCount;
        }
    }
}
